/** 
 * Project Name:tinysql 
 * File Name:SqlBuilder.java 
 * Package Name:com.wdy.golden.tinysql.plus.builder 
 * Date:2021年4月26日下午4:58:56 
 * 
*/  
package com.wdy.golden.tinysql.plus.builder;

import com.wdy.golden.tinysql.constant.TinysqlConstants;
import com.wdy.golden.tinysql.plus.ConditionSymbol;
import com.wdy.golden.tinysql.plus.HavingConditionSymbol;
import com.wdy.golden.tinysql.plus.OrderBySymbol;
import com.wdy.golden.tinysql.plus.util.PlusClassUtil;

/** 
 * ClassName:SqlBuilder <br/> 
 * Function: TODO ADD FUNCTION. <br/> 
 * Reason:   TODO ADD REASON. <br/> 
 * Date:     2021年4月26日 下午4:58:56 <br/> 
 * @author   DongYe_Wu 
 * @version
 * @param <T>
 * @see       
 */
public class SqlBuilder<T> {
	
	private T entity;
	private Class<?> entityClazz;
	
	private String rowStartParam;
	private String rowEndParam;
	private boolean secondCache = false;
	private int cacheSeconds;
	
	private StringBuffer sb = new StringBuffer();
	
	//--------------
	public SqlBuilder(T entity) {
		this.entity = entity;
	}
	public SqlBuilder(Class<?> entityClazz) {
		this.entityClazz = entityClazz;
	}

	//--------------
	/**
	 * 
	 * builder:(用于update,insert这种需要根据pojo对象去做操作的). <br/> 
	 * 
	 * @author E052535 
	 * @param <T>
	 * @param entity 操作表对应的pojo对象
	 * @return
	 */
	public static <T> SqlBuilder<?> builder(T entity){
        return new SqlBuilder<Object>(entity);
    }
	/**
	 * 
	 * builder:(用于select,delete这种，只需要pojo class就可进行操作的). <br/> 
	 * 
	 * @author E052535 
	 * @param <T>
	 * @param entityClazz 操作表对应pojo对象的class
	 * @return
	 */
	public static <T> SqlBuilder<?> builder(Class<?> entityClazz){
        return new SqlBuilder<Object>(entityClazz);
    }

	//------------------------
	/**
	 * 
	 * delete:(返回delete sql片段: DELETE). <br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> delete() {
		sb.append(" DELETE ");
		return this;
	}
	//
	/**
	 * 
	 * select:(返回select sql片段: SELECT). <br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> select() {
		sb.append(" SELECT ");
		return this;
	}
	/**
	 * 
	 * selectCount:(返回select sql片段: SELECT COUNT(*) ). <br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> selectCount() {
		sb.append(" SELECT COUNT(*) as CNT ");
		return this;
	}
	/**
	 * 
	 * selectAll:(返回select sql片段: SELECT 与所有表字段名，@TinyIgnore的字段除外). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> selectAll() {
		sb.append(" SELECT "+getColumns());
		return this;
	}
	/**
	 * 
	 * queryColumn:(返回select字段 sql片段，用于非最后一个字段，如：COLUMN1,COLUMN2,COLUMN3, ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> queryColumn(String fieldName) {
		sb.append(getColumnName(fieldName)+",");
		return this;
	}
	/**
	 * 
	 * queryColumnEnd:(返回select字段 sql片段，用于最后一个字段，如：COLUMN10). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> queryColumnEnd(String fieldName) {
		sb.append(getColumnName(fieldName));
		return this;
	}
	
	/**
	 * 
	 * queryFixColumn:(返回固定的select字段 sql片段，用于非最后一个字段，如：count(1) as cnt). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fixCloumn
	 * @return
	 */
	public SqlBuilder<T> queryFixColumn(String fixCloumn) {
		sb.append(fixCloumn+",");
		return this;
	}
	
	/**
	 * 
	 * queryFixColumnEnd:(返回固定的select字段 sql片段，用于最后一个字段，如：count(1) as cnt). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fixCloumn
	 * @return
	 */
	public SqlBuilder<T> queryFixColumnEnd(String fixCloumn) {
		sb.append(fixCloumn);
		return this;
	}
	
	/**
	 * 
	 * from:(返回from sql片段，如：FROM table1 ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> from() {
		sb.append(" FROM "+getTableName());
		return this;
	}
	/**
	 * 
	 * where:(返回where sql片段  WHERE 1=1 ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> where() {
		sb.append(" WHERE 1=1 ");
		return this;
	}
	/**
	 * 
	 * condition:(返回where条件sql片段，如：AND COLUMN1 IS NULL AND COLUMN2>:column2等，用于IS NULL或 IS NOT NULL或字段与属性是映射关系的). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName 判断条件/判定字段的对应的pojo属性
	 * @param cond 判断条件，是个枚举类型ConditionSymbol
	 * @return
	 */
	public SqlBuilder<T> condition(String fieldName, ConditionSymbol cond) {
		if(
				cond==ConditionSymbol.ISNOTNULL
				||
				cond==ConditionSymbol.ISNULL
		) {
			sb.append(" AND "+getColumnName(fieldName)+" "+cond.name);
		}else {
			String condStr = String.format(cond.name, fieldName);
			sb.append(" AND "+getColumnName(fieldName)+" "+condStr);
		}
		return this;
	}
	/**
	 * 
	 * condition:(返回where条件sql片段，如：AND COLUMN1 IN (:ids) AND COLUMN2 NOT IN (:names)等，用于字段与属性是非映射关系的). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName 判断条件字段的对应的pojo属性
	 * @param cond 判断条件，是个枚举类型ConditionSymbol
	 * @param condFieldName 判定的pojo属性
	 * @return
	 */
	public SqlBuilder<T> condition(String fieldName, ConditionSymbol cond, String condFieldName) {
		String condStr = String.format(cond.name, condFieldName);
		sb.append(" AND "+getColumnName(fieldName)+" "+condStr);
		return this;
	}
	/**
	 * 
	 * orderBy:(返回order by sql片段 ORDER BY). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> orderBy() {
		sb.append(" ORDER BY ");
		return this;
	}
	/**
	 * 
	 * orderByColumn:(返回order by 字段sql片段，用于非最后一个字段，如：COLUMN1 ASC,COLUMN2 DESC,  ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @param ord
	 * @return
	 */
	public SqlBuilder<T> orderByColumn(String fieldName, OrderBySymbol ord) {
		sb.append(getColumnName(fieldName)+" "+ord.name+",");
		return this;
	}
	/**
	 * 
	 * orderByColumnEnd:(返回order by 字段sql片段，用于最后一个字段，如：COLUMN3 ASC  ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @param ord
	 * @return
	 */
	public SqlBuilder<T> orderByColumnEnd(String fieldName, OrderBySymbol ord) {
		sb.append(getColumnName(fieldName)+" "+ord.name);
		return this;
	}
	/**
	 * 
	 * groupBy:(返回group by sql片段 GROUP BY). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> groupBy() {
		sb.append(" GROUP BY ");
		return this;
	}
	/**
	 * 
	 * groupByColumn:(返回group by sql片段，用于非最后一个group字段，如COLUMN1,COLUMN2,  ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> groupByColumn(String fieldName) {
		sb.append(getColumnName(fieldName)+",");
		return this;
	}
	/**
	 * 
	 * groupByColumnEnd:(返回group by sql片段，用于最后一个group字段，如COLUMN1  ). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> groupByColumnEnd(String fieldName) {
		sb.append(getColumnName(fieldName));
		return this;
	}
	/**
	 * 
	 * havingColumn:(返回having语句sql片段，如：HAVING SUM(COLUMN1)>100). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param havingFunc having函数，如 SUM(%s),AVG(%s)，这里的%s会被fieldName对应的字段名替换
	 * @param fieldName having字段对应的pojo属性名
	 * @param cond having条件
	 * @param havingValue having的值
	 * @return
	 */
	public SqlBuilder<T> havingColumn(String havingFunc, String fieldName, HavingConditionSymbol cond, int havingValue) {
		String columnName = getColumnName(fieldName);
		String funcStr = String.format(havingFunc, columnName);
		String condStr = String.format(cond.name, havingValue);
		sb.append(" HAVING "+funcStr+condStr+" ");
		return this;
	}
	/**
	 * 
	 * havingCount:(生成having count(*)语句sql片段，如：HAVING COUNT(*)>15). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param cond having count的条件
	 * @param countValue having count的值
	 * @return
	 */
	public SqlBuilder<T> havingCount(HavingConditionSymbol cond, int countValue) {
		String condStr = String.format(cond.name, countValue);
		sb.append(" HAVING COUNT(*)"+condStr+" ");
		return this;
	}
	//
	/**
	 * 
	 * insert:(返回insert语句). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> insert() {
		sb.append(" INSERT INTO "+getTableName()+ " ("+getColumns()+") VALUES ("+getValueFields()+") ");
		return this;
	}

	/**
	 * 
	 * insert:(返回insert语句,主键使用传入的sequence来生成). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param sequenceNextVal
	 * @return
	 */
	public SqlBuilder<T> insert(String sequenceNextVal) {
		sb.append(" INSERT INTO "+getTableName()+ " ("+getColumns()+") VALUES ("+getValueFields(sequenceNextVal)+") ");
		return this;
	}
	//
	/**
	 * 
	 * update:(返回update语句sql片段，如： UPDATE TABLE1). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> update() {
		sb.append(" UPDATE "+getTableName());
		return this;
	}
	/**
	 * 
	 * updateAll:(返回update语句sql片段，如：UPDATE TABLE1 与所有表字段名，@TinyIgnore的字段除外). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> updateAll() {
		sb.append(" UPDATE "+getTableName()+" SET "+getSetAll());
		return this;
	}
	/**
	 * 
	 * set:(返回update语句sql片段 SET). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public SqlBuilder<T> set() {
		sb.append(" SET ");
		return this;
	}
	/**
	 * 
	 * setColumn:(返回update set语句sql片段，用于非最后一个group字段，如：COLUMN1=:column1,COLUMN2=:column2,). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> setColumn(String fieldName) {
		sb.append(getColumnName(fieldName)+" = :"+fieldName+",");
		return this;
	}
	/**
	 * 
	 * setColumnEnd:(返回update set语句sql片段，用于最后一个group字段，如：COLUMN3=:column3). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param fieldName
	 * @return
	 */
	public SqlBuilder<T> setColumnEnd(String fieldName) {
		sb.append(getColumnName(fieldName)+" = :"+fieldName);
		return this;
	}
	
	/**
	 * 
	 * paginationRowStartParam:(指定分页用起始行参数用po的哪个字段). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param rowStartParam
	 * @return
	 */
	public SqlBuilder<T> paginationRowStartParam(String rowStartParam) {
		this.rowStartParam = rowStartParam;
		return this;
	}

	public SqlBuilder<T> paginationDefaultRowStartParam() {
		this.rowStartParam = TinysqlConstants.DEFAULT_PAGINATION_ROWSTART;
		return this;
	}
	
	/**
	 * 
	 * paginationRowEndParam:(指定分页用结束行参数用po的哪个字段). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param rowEndParam
	 * @return
	 */
	public SqlBuilder<T> paginationRowEndParam(String rowEndParam) {
		this.rowEndParam = rowEndParam;
		return this;
	}
	
	public SqlBuilder<T> paginationDefaultRowEndParam() {
		this.rowEndParam = TinysqlConstants.DEFAULT_PAGINATION_ROWEND;
		return this;
	}
	
	/**
	 * 
	 * cacheSeconds:(指定select语句是否使用二级缓存，调用该方法后会将结果集缓存到redis). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @param seconds 缓存过期时间，单位：秒
	 * @return
	 */
	public SqlBuilder<T> cacheSeconds(int seconds) {
		this.secondCache = true;
		this.cacheSeconds = seconds;
		return this;
	}
	
	//
	/**
	 * 
	 * buildSql:(构建sql语句). <br/> 
	 * TODO(这里描述这个方法适用条件 – 可选).<br/> 
	 * TODO(这里描述这个方法的执行流程 – 可选).<br/> 
	 * TODO(这里描述这个方法的使用方法 – 可选).<br/> 
	 * TODO(这里描述这个方法的注意事项 – 可选).<br/> 
	 * 
	 * @author E052535 
	 * @return
	 */
	public String buildSql() {
		return sb.toString();
	}
	//------------------
	public T getEntity() {
		return entity;
	}	
	public String getRowStartParam() {
		return rowStartParam;
	}
	public String getRowEndParam() {
		return rowEndParam;
	}
	public boolean isSecondCache() {
		return secondCache;
	}
	public int getCacheSeconds() {
		return cacheSeconds;
	}
	//----------------
	private String getTableName() {
		if(this.entity==null) {
			return PlusClassUtil.getTableName(this.entityClazz);
		}else {
			return PlusClassUtil.getTableName(this.entity.getClass());
		}
	}
	private String getColumnName(String fieldName) {
		if(this.entity==null) {
			return PlusClassUtil.getColumnName(this.entityClazz, fieldName);
		}else {
			return PlusClassUtil.getColumnName(this.entity.getClass(), fieldName);
		}
	}
	private String getColumns() {
		if(this.entity==null) {
			return PlusClassUtil.getColumns(this.entityClazz);
		}else {
			return PlusClassUtil.getColumns(this.entity.getClass());
		}
	}
	private String getValueFields() {
		if(this.entity==null) {
			return PlusClassUtil.getValueFields(this.entityClazz);
		}else {
			return PlusClassUtil.getValueFields(this.entity.getClass());
		}
	}
	private String getValueFields(String sequenceNextVal) {
		if(this.entity==null) {
			return PlusClassUtil.getValueFields(this.entityClazz, sequenceNextVal);
		}else {
			return PlusClassUtil.getValueFields(this.entity.getClass(), sequenceNextVal);
		}
	}
	private String getSetAll() {
		if(this.entity==null) {
			return PlusClassUtil.getSetAll(this.entityClazz);
		}else {
			return PlusClassUtil.getSetAll(this.entity.getClass());
		}
	}
}
